#include "Task.hpp"
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <cassert>
#include <vector>
#include <cstring>
#include <ctime>
#include <sys/wait.h>


const int processnum  = 5; // 5个子进程
std::vector<task_t> tasks; // 任务队列


struct channel{
public:
    channel(int cmfd, pid_t slaverid, std::string processnum)
        : _cmfd(cmfd), _slaverid(slaverid), _processnum(processnum) {}
    void setSlaverid(pid_t slaverid) { _slaverid = slaverid; } // 设置子进程的pid
    int getCmfd() const { return _cmfd; } // 获取管道的写端
    pid_t getSlaverid() const { return _slaverid; } // 获取子进程的pid
    std::string getProcessnum() const { return _processnum; } // 获取子进程的名字
private:
    int _cmfd;               // 发送任务的文件描述符
    pid_t _slaverid;         // 子进程的pid
    std::string _processnum; // 子进程的名字 用于打印日志
};

void slaver(){ 
    while(true){
        int cmdcode = 0; // 任务编号

        // 如果父进程不给子进程发送数据，会阻塞等待，直到父进程发送数据
        int n = read(0, &cmdcode, sizeof(int));  // 从文件描述符0读取rfd，因为此时文件描述符0已经重定向到了 pipefd[0]
                                                 // 此时是从管道的读端读取数据，即读取管道的数据，而且一次读 4字节
        if(n == sizeof(int)){
            std::cout << "son " << getpid() << " get " << cmdcode << "from " << getppid() << std::endl;
            if (cmdcode >= 0 && tasks.size() > cmdcode){ // 任务编号有效
                tasks[cmdcode](); // 执行任务  
            }  
        }
        if (n == 0) break;  // 读到0，说明写端关闭了，则退出子进程
        sleep(1);
    } 
}

void InitProcessPool(std::vector<channel>* channels){
    for (int i = 0; i < processnum; ++i){
        int pipefd[2]; // 创建管道  临时变量，但存入vector中就持久化了
        int n = pipe(pipefd); // pipefd[0]是读端，pipefd[1]是写端
        assert(n == 0); // 创建管道失败  assert 只有在debug有效 因此 g++ 要加-g 编译
        
        channels->push_back(channel(pipefd[1], 0, "pricess-" + std::to_string(i))); // 将管道的写端和子进程的pid存入vector中 
        pid_t pid = fork(); // 创建子进程
        assert(pid >= 0);   // 创建子进程失败
        if (pid == 0){      // 子进程
            for (auto&  c : *channels){
                if (c.getProcessnum() == "pricess-" + std::to_string(i)){ // 找到当前子进程的信息   
                    c.setSlaverid(pid); // 更新子进程的pid
                    break;
                }
            }// 遍历vector，找到当前子进程的信息

            close(pipefd[1]); // 关闭写端
            dup2(pipefd[0], 0); // 将读端重定向到文件描述符0  此时文件描述符0已经重定向到了 管道读端
            slaver();  // 子函数执行的函数，传参传 读端
            std::cout << "son " << getpid() << " exit" << std::endl;
            exit(0); // 退出子进程
        }

        close(pipefd[0]); // 关闭读端 
        // channels->push_back(channel(pipefd[1], pid, "pricess-" + std::to_string(i))); // 将管道的写端和子进程的pid存入vector中 
    }
}

void DebugPrint(std::vector<channel>& channels){
    for (int i = 0; i < channels.size(); ++i){
        std::cout << "process id: " << channels.at(i).getSlaverid() << " ";
        std::cout << "process name: " << channels.at(i).getProcessnum() << std::endl;
    }
}


// 全局或静态变量
static int rr_index = 0;  // 轮询索引

int get_next_process_round_robin(int processnum) {
    int selected = rr_index;
    rr_index = (rr_index + 1) % processnum;  // 移动到下一个进程，循环
    return selected;
}


int main(){
    srand(time(nullptr)^getpid()^1023);   // 随机数种子
    LoadTask(&tasks); // 加载任务   


    std::vector<channel> channels; // 创建5个子进程
    // 1. 初始化进程池
    InitProcessPool(&channels); // 初始化进程池
    DebugPrint(channels); // 打印进程池的信息

    int cnt = 10;
    // 2. 开始控制子进程
    // 向管道写入多个请求
    while (cnt --){  // 50次任务请求
        // 1. 选择任务
        int cmdcode = rand() % 10; // 随机生成一个任务

        // 2. 选择进程   (从vector选择一个进程)
        // // 2.1. 随机选择一个子进程
        // int processindex = rand() % processnum; 
        // 2.2. 轮询选择子进程
        int processindex = get_next_process_round_robin(processnum);

        int wfd = channels.at(processindex).getCmfd(); // 获取管道的写端
        std::cout << "father " << getpid() << " send to " <<  channels.at(processindex).getSlaverid() << " with " << cmdcode << std::endl;

        // 发送任务
        write(wfd, &cmdcode, sizeof(int)); // 将任务写入管道  一次写 4字节    
    }

    // 等待所有子进程完成
    for (int i = 0; i < processnum; ++i) {
        close(channels[i].getCmfd()); // 关闭管道的写端, 之后子进程会读到0，然后自动退出
    }
    for (int i = 0; i < processnum; ++i) {
        pid_t pid = waitpid(channels[i].getSlaverid(), nullptr, 0); // 等待子进程完成
    }

    return 0;
}